from .cement_batching import (transform_ci_to_nl, convert_str_to_float,
                              integrate_gypsum_into_chemical, wet_to_dry, calc_balance_dry_from_feedback_dry, get_coef)
from . import config
import pymongo
import numpy as np


def process_adm_ratio_in_predict(current_data, adm_ratio):
    if adm_ratio != {}:
        feedback_wet = current_data["dcs_feedback"]

        for k, v in adm_ratio.items():
            sum_num_feedback = feedback_wet[k]

            deno = sum(v.values())
            for k_1, v_1 in v.items():
                feedback_wet[k_1] = sum_num_feedback * v_1 / deno


def predict_cement_strength(model_type, model_code, data):
    # 1.2 类型转换
    data = convert_str_to_float(data)
    # 1.3 扁平化处理
    (clinker_strength, gyp_ratio, admixture_ratio, materials_chemical_dict, current_data) = data.values()  # 增加一天实测值

    current_process_quality = current_data['process_quality']
    current_DCS_feedback = current_data['dcs_feedback']

    process_adm_ratio_in_predict(current_data, admixture_ratio)

    # 1.2 数据校验
    # 化学成份 价格

    cement_1_d = 0
    cement_3_d = 0

    chemical_key = set(data['materials_chemical_dict'].keys())
    if model_type == 2:
        cement_3_d = current_data["cement_real_strength_3d"]
    elif model_type == 1:
        cement_1_d = current_data["cement_real_strength_1d"]

    gyp_key = set(data['gyp_ratio'].keys())
    adm_key = set(data["admixture_ratio"].keys())
    for k, v in data["admixture_ratio"].items():
        adm_key = adm_key.union(set(v.keys()))

    # 1.3 石膏
    # 抽象石膏
    abs_gyp_flag = True if "石膏" in current_DCS_feedback.keys() else False
    if abs_gyp_flag:
        integrate_gypsum_into_chemical(materials_chemical_dict, gyp_ratio)

    # 具体石膏
    con_gyp_flag = False
    con_gyp_list = []
    gypsum_threshold = config.gyp_thre
    for i in materials_chemical_dict.keys():
        if materials_chemical_dict[i]["SO3"] > gypsum_threshold and i in current_DCS_feedback.keys():
            con_gyp_flag = True
            con_gyp_list.append(i)

    # 2 强度预测
    # 2.1 转换为物料平衡干基
    materials_chemical_dict = {material: {key: value / 100 for key, value in properties.items()} for
                               material, properties in materials_chemical_dict.items()}
    print(current_DCS_feedback)
    print("materials_chemical_dict:", materials_chemical_dict)
    feedback_dry = wet_to_dry(current_DCS_feedback, materials_chemical_dict)

    print("feedback_dry", feedback_dry)

    balance_dry = calc_balance_dry_from_feedback_dry(feedback_dry, current_process_quality, materials_chemical_dict)

    # 取系数逻辑
    app_set = chemical_key - gyp_key
    params_3d, params_28d, intercept_3d, intercept_28d, cement_list = get_coef(model_type, model_code, app_set,
                                                                               materials_chemical_dict, abs_gyp_flag,
                                                                               con_gyp_flag, con_gyp_list)  # 增加一天实测值
    print("本次涉及到的物料有：", app_set)
    print("使用params_3d", params_3d)
    print("使用params_28d", params_28d)
    print("intercept_3d", intercept_3d)
    print("intercept_28d", intercept_28d)

    materials_list = app_set
    process_list = ["细度45μm", "比表", "SO3"]

    strength_3d, strength_28d = calc_strength(clinker_strength, model_type, balance_dry, current_process_quality,
                                              params_3d, params_28d, materials_list, process_list, cement_1_d,
                                              cement_3_d, cement_list, intercept_3d, intercept_28d)

    return round(strength_3d, 1), round(strength_28d, 1)


def calc_strength(clinker_strength, model_type, balance_dry, process, strength_3d_params, strength_28d_params,
                  materials_list, process_list, cement_1_d, cement_3_d, cement_list, intercept_3d, intercept_28d):
    '''
    计算水泥强度，有两种方法： 根据SO3的计算方法不同，采用不同的计算方法
    1. SO3 根据物料计算而来）
    '''
    strength_3d = 0
    strength_28d = 0
    flag = True  # 增加一天实测值
    balance_dry = {k: v / 100 for k, v in balance_dry.items()}
    for mat in materials_list:
        try:
            strength_3d += balance_dry[mat] * strength_3d_params[mat] * clinker_strength['熟料3天强度预测']
            strength_28d += balance_dry[mat] * strength_28d_params[mat] * clinker_strength['熟料28天强度预测']
        except KeyError:
            flag = False
            break
    if flag:
        for i in process_list:
            strength_3d += process[i] * strength_3d_params[i]
            strength_28d += process[i] * strength_28d_params[i]
    print("strength_3d", strength_3d)
    print("strength_28d", strength_28d)
    if model_type == 1:
        strength_3d = isNone(flag, strength_3d, clinker_strength['熟料3天强度预测'], intercept_3d,
                             cement_1_d * cement_list[0])
        strength_28d = isNone(flag, strength_28d, clinker_strength['熟料28天强度预测'], intercept_28d,
                              cement_1_d * cement_list[1])
    else:
        strength_3d = isNone(flag, strength_3d, clinker_strength['熟料3天强度预测'], intercept_3d, 0)
        strength_28d = isNone(flag, strength_28d, clinker_strength['熟料28天强度预测'], intercept_28d, 0)
        if model_type == 2:
            strength_28d = strength_28d + cement_3_d * cement_list[0]

    return strength_3d, strength_28d


def float_int(x):
    if isinstance(x, float):
        return int(x)


def isNone(flag, x1, x2, x3, x4):
    if flag:
        return x1 + x2 + x3 + x4  # 增加一天实测值
    else:
        return 0


def cal_acc(data):
    # 初始化计数器
    total_3d = 0
    correct_3d = 0
    total_28d = 0
    correct_28d = 0

    # 遍历数组，计算 3 天和 28 天预测的准确率
    for row in data:
        if not row:
            continue
        actual_3d = row[1]
        predicted_3d = row[2]
        actual_28d = row[3]
        predicted_28d = row[4]

        # 计算 3 天预测的准确率
        if actual_3d is not None and predicted_3d is not None:
            try:
                actual_3d = float(actual_3d)
                total_3d += 1
                if abs(actual_3d - predicted_3d) <= 1.2:  # 假设预测值与实测值的误差在 1 .2以内视为准确
                    correct_3d += 1
            except ValueError:
                print(f"无法将 actual_3d 转换为浮点数: {actual_3d}")

        # 计算 28 天预测的准确率
        if actual_28d is not None and predicted_28d is not None:
            try:
                actual_28d = float(actual_28d)
                total_28d += 1
                if abs(actual_28d - predicted_28d) <= 1.5:  # 假设预测值与实测值的误差在 1.5 以内视为准确
                    correct_28d += 1
            except ValueError:
                print(f"无法将 actual_28d 转换为浮点数: {actual_28d}")

    # 计算准确率
    accuracy_3d = correct_3d / total_3d if total_3d > 0 else 0
    accuracy_28d = correct_28d / total_28d if total_28d > 0 else 0

    return accuracy_3d, accuracy_28d


def result_plt(y, y_preds, thresher, filename):  # -> tuple[Float | ndarray, Float | ndarray, Float | ndarray, ...:

    import matplotlib.pyplot as plt
    from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

    # 检查并处理 NaN 值
    valid_indices = ~np.isnan(y) & ~np.isnan(y_preds)
    y = y[valid_indices]
    y_preds = y_preds[valid_indices]
    # Evaluation metrics
    mse = mean_squared_error(y, y_preds)
    mae = mean_absolute_error(y, y_preds)
    r2 = r2_score(y, y_preds)
    within_threshold = (np.abs(y - y_preds) <= thresher) * 100
    percentage_within_threshold = np.mean(within_threshold)
    mean = y.mean()
    std_dev = y.std()
    cv = (std_dev / mean) * 100

    # Plotting
    fig_width = max(10, len(list(y_preds)) / 5)  # 宽度至少为10，高度可以根据 y 的长度变化
    fig_height = 7  # 高度固定为7
    plt.figure(figsize=(fig_width, fig_height))
    plt.plot(y, label='True Values', marker='o')
    plt.plot(y_preds, label='Predictions', marker='x')

    for i, true_value in enumerate(y):
        plt.plot([i, i], [true_value - thresher, true_value + thresher], color='grey', linestyle='--', linewidth=0.5)
    plt.title(
        f'MSE: {mse:.2f}, MAE: {mae:.2f}, R^2: {r2:.2f}\n Global ±{thresher:.1f}: {percentage_within_threshold:.2f}%, CV: {cv:.2f}%')
    plt.xlabel('Sample Index')
    plt.ylabel('Y Value')
    plt.legend()
    # 自动调整布局
    plt.tight_layout()

    plt.show()
    return mse, mae, r2, percentage_within_threshold


def cement_strength_predict(model_code, item):
    # 初始化一个空字典
    result = {
        "linear": {
            "no_real_strength": {},
            "with_real_strength_1d": {},
            "with_real_strength_3d": {}
        }
    }
    # 定义键的列表
    keys = ['CI_10001103', 'CI_10001104']
    data = {
        "clinker_strength": {
            "熟料3天强度预测": item['clinker_strength']['熟料3天强度预测'],
            "熟料28天强度预测": item['clinker_strength']['熟料28天强度预测']
        },
        "gyp_ratio": item['gyp_ratio'],
        "admixture_ratio": item["admixture_ratio"],
        "materials_chemical_dict": item['materials_chemical_dict'],
        "current_data": {
            "process_quality": item["current_data"]['process_quality'],
            "dcs_feedback": item["current_data"]['dcs_feedback'],
            "cement_real_strength_1d": item["current_data"]['cement_real_strength_1d'],
            "cement_real_strength_3d": item["current_data"]['cement_real_strength_3d']
        }
    }

    try:
        strength_3d_linear, strength_28d_linear = predict_cement_strength(0, model_code, data)
        result["linear"]["no_real_strength"] = {keys[0]: str(strength_3d_linear), keys[1]: str(strength_28d_linear),
                                                "error_msg": ""}
    except Exception as e:
        print("===================== Error：线性模型无实测入参模型预测失败，报错原因：{} =================".format(e))
        result["linear"]["no_real_strength"] = {"error_msg": str(e)}
    if data["current_data"]["cement_real_strength_1d"] is not None:
        try:
            strength_3d_linear_1d, strength_28d_linear_1d = predict_cement_strength(1, model_code, data)
            result["linear"]["with_real_strength_1d"] = {keys[0]: str(strength_3d_linear_1d),
                                                         keys[1]: str(strength_28d_linear_1d), "error_msg": ""}
        except Exception as e:
            print("===================== Error：线性模型1天实测入参模型预测失败，报错原因：{} =================".format(e))
            result["linear"]["with_real_strength_1d"] = {"error_msg": str(e)}

    if data["current_data"]["cement_real_strength_3d"] is not None:
        try:
            strength_3d_linear_3d, strength_28d_linear_3d = predict_cement_strength(2, model_code, data)
            result["linear"]["with_real_strength_3d"] = {keys[1]: str(strength_28d_linear_3d), "error_msg": ""}
        except Exception as e:
            print("===================== Error：线性模型3天实测入参模型预测失败，报错原因：{} =================".format(e))
            result["linear"]["with_real_strength_3d"] = {"error_msg": str(e)}

    return result
